# startup-mysql.pm
# Copyright (C) 2006,2007  Stephane Alnet
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# 

#
# For more information visit http://carrierclass.net/
#
use strict; use warnings;

package CCNCore::Provision::startup_mysql;
use base qw(CCNCore::Provision::Distributed);

use CCNCore::Scripting;
use CCNCore::Provision; # populate_script

sub run
{
    my $self = shift;

}

# Gather the servers for a given db_clusters.pk
sub get_servers($)
{
    my $self = shift;

    $self->{servers} = [ $self->sql_get_array(<<'SQL',$self->p->{db_member}) ] unless exists $self->{servers};
        SELECT id FROM server WHERE db_member = ? ORDER BY id ASC
SQL

    return $self->{servers};
}

sub run_into($)
{
    my $self = shift;
    my $sql  = shift;
    
    $self->log("Running $sql\n");

    my $into_db = new CCN::Database("DBI:mysql:","","");
    if( $into_db and $into_db->is_connected )
    {
        $into_db->sql_do($sql);
    }
    else
    {
        warn "Database connection failed" ;
    }
}

# -------------- Startup process ------------------ 

use CCNCore::IPC::Ticket;

# Master: Request all servers in the cluster to start the process.
sub get_started($$)
{
    my $self = shift;
    my $master_ticket   = pop;
    my $master_callback = pop;

    $self->log("Not a primary DB server\n"),
    return
        unless defined $self->p->{db_member} and $self->p->system_pk == $self->p->primary_db_server;

    $self->log("Primary DB server\n");

    # .force-sql-bootstrap is set in Provision::Application::mysql if my.cnf was changed
    # (normally because a server was added or removed from the cluster).
    use CCNCore::Home;
    if( $self->bootstrapping or -e CCNCore::Home::config.'/local/.force-sql-bootstrap')
    {
        unlink CCNCore::Home::config.'/local/.force-sql-bootstrap';
        my @servers = @{$self->get_servers()};

        my $t = new CCNCore::IPC::Ticket;
        $t->add_action($#servers+1,'stop-and-reset-slave-completed');
        $t->add_context('master-ticket',$master_ticket);
        $t->add_context('master-callback',$master_callback);

        foreach(0..$#servers)
        {
            $self->spool_this($servers[$_],$t->ticket,'stop-and-reset-slave');
        }
        $self->log("Starting database boostratpping\n");
    }
    else
    {
        $self->populate_and_continue($master_callback,$master_ticket);
    }
}

use CCNCore::Provision::Unspool;

# Client: Process
sub stop_and_reset_slave($$)
{
    my $self = shift;
    my $ticket   = pop;
    my $callback = pop;

    $self->die ("no valid ticket") unless defined $ticket and $ticket =~ /^\d+$/;
    $self->die ("no valid callback") unless defined $callback and $callback =~ /^\d+$/;

    my @servers = @{$self->get_servers()};

    if($#servers+1 >= 2)
    {
        $self->run_into('STOP SLAVE');
        $self->run_into('RESET SLAVE');
    }

    my $s = new CCNCore::Provision::Unspool($self->p,$callback,$ticket);
    $s->reply();
}

# Master:
sub stop_and_reset_slave_completed($)
{
    my $self = shift;
    my $ticket_id = shift;

    $self->die ("no valid ticket") unless defined $ticket_id and $ticket_id =~ /^\d+$/;

    # Retrieve information from the ticket then destroy it.
    my $t = new CCNCore::IPC::Ticket($ticket_id);
    my $master_ticket   = $t->retrieve_context('master-ticket');
    my $master_callback = $t->retrieve_context('master-callback');
    $t->completed();
    $t->clear_ticket();

    # Dump the current content of the databases
    $self->p->dump_dbs();

    # Open a new ticket
    $t = new CCNCore::IPC::Ticket;

    my @servers = @{$self->get_servers()};
    $t->add_action(defined $servers[1] ? 2 : 1 , 'drop-and-reset-master-completed');
    $t->add_context('master-ticket',$master_ticket);
    $t->add_context('master-callback',$master_callback);

    $self->spool_this($servers[0],$t->ticket,'drop-and-reset-master');
    $self->spool_this($servers[1],$t->ticket,'drop-and-reset-master') if defined $servers[1];
}

# Client:
sub drop_and_reset_master($$)
{
    my $self = shift;
    my $ticket   = pop;
    my $callback = pop;

    $self->die ("no valid ticket") unless defined $ticket and $ticket =~ /^\d+$/;
    $self->die ("no valid callback") unless defined $callback and $callback =~ /^\d+$/;

    my @db_names = $self->p->get_db_names;
    for my $db (@db_names)
    {
        $self->run_into(qq(DROP DATABASE IF EXISTS "$db"));
    };

    my @servers = @{$self->get_servers()};
    if($#servers+1 >= 2)
    {
        $self->run_into('RESET MASTER;');
    }


    my $s = new CCNCore::Provision::Unspool($self->p,$callback,$ticket);
    $s->reply();
}

# Master:
sub drop_and_reset_master_completed($)
{
    my $self = shift;
    my $ticket_id = shift;

    $self->die ("no valid ticket") unless defined $ticket_id and $ticket_id =~ /^\d+$/;

    # Retrieve information from the ticket then destroy it.
    my $t = new CCNCore::IPC::Ticket($ticket_id);
    my $master_ticket   = $t->retrieve_context('master-ticket');
    my $master_callback = $t->retrieve_context('master-callback');
    $t->completed();
    $t->clear_ticket();

    # Since we are the primary, do create_databases now.
    my @db_names = $self->p->get_db_names;
    for my $db (@db_names)
    {
        $self->run_into(qq(CREATE DATABASE "$db"));
    };

    # Open a new ticket
    $t = new CCNCore::IPC::Ticket;

    my @servers = @{$self->get_servers()};
    $t->add_action($#servers+1, 'reset-and-start-slave-completed');
    $t->add_context('master-ticket',$master_ticket);
    $t->add_context('master-callback',$master_callback);

    foreach(0..$#servers)
    {
        $self->spool_this($servers[$_],$t->ticket,'reset-and-start-slave');
    }
}

# Client:
sub reset_and_start_slave($$)
{
    my $self = shift;
    my $ticket   = pop;
    my $callback = pop;

    $self->die ("no valid ticket") unless defined $ticket and $ticket =~ /^\d+$/;
    $self->die ("no valid callback") unless defined $callback and $callback =~ /^\d+$/;

    my @servers = @{$self->get_servers()};
    
    # Note: actually the proper thing to do would be to start it on the two masters first,
    # then the copying slaves.
    if($#servers+1 >= 2)
    {
        $self->run_into('RESET SLAVE');
        $self->run_into('START SLAVE');
    }

    my $s = new CCNCore::Provision::Unspool($self->p,$callback,$ticket);
    $s->reply();
}

# Master:
sub reset_and_start_slave_completed($)
{
    my $self = shift;
    my $ticket_id = shift;

    $self->die ("no valid ticket") unless defined $ticket_id and $ticket_id =~ /^\d+$/;

    # Retrieve information from the ticket then destroy it.
    my $t = new CCNCore::IPC::Ticket($ticket_id);
    my $master_ticket   = $t->retrieve_context('master-ticket');
    my $master_callback = $t->retrieve_context('master-callback');
    $t->completed();
    $t->clear_ticket();

    # Restore the databases content
    $self->p->restore_dbs();

    # Open a new ticket
    $t = new CCNCore::IPC::Ticket;

    my @servers = @{$self->get_servers()};
    $t->add_action(defined $servers[1] ? 2 : 1 , 'complete-grants-completed');
    $t->add_context('master-ticket',$master_ticket);
    $t->add_context('master-callback',$master_callback);

    $self->spool_this($servers[0],$t->ticket,'complete-grants');
    $self->spool_this($servers[1],$t->ticket,'complete-grants') if defined $servers[1];
}

# Client:
sub complete_grants($$)
{
    my $self = shift;
    my $ticket   = pop;
    my $callback = pop;

    $self->die ("no valid ticket") unless defined $ticket and $ticket =~ /^\d+$/;
    $self->die ("no valid callback") unless defined $callback and $callback =~ /^\d+$/;

    my $password = $self->make_password($self->p->{db_member},$self->p->server_pk,$self->system_salt,'ccn-manager');
    
    $self->run_into(qq(GRANT ALL ON TABLE *.* TO "ccn-manager" IDENTIFIED BY '$password' WITH GRANT OPTION));
    $self->run_into(q(DELETE FROM mysql.user WHERE User = ''));

    my $s = new CCNCore::Provision::Unspool($self->p,$callback,$ticket);
    $s->reply();
}

# Master:
sub complete_grants_completed($)
{
    my $self = shift;
    my $ticket_id = shift;

    $self->die ("no valid ticket") unless defined $ticket_id and $ticket_id =~ /^\d+$/;

    # Retrieve information from the ticket then destroy it.
    my $t = new CCNCore::IPC::Ticket($ticket_id);
    my $master_ticket   = $t->retrieve_context('master-ticket');
    my $master_callback = $t->retrieve_context('master-callback');
    $t->completed();
    $t->clear_ticket();
    
    $self->populate_and_continue($master_callback,$master_ticket);
}

sub populate_and_continue($$)
{
    my $self = shift;
    my ($master_callback,$master_ticket) = @_;
    
    # Now that the databases and servers are ready, create/update/populate them.
    warn "populate_and_continue: executing $CCNCore::Provision::populate_script";
    CCN::Scripting::exec_command($CCNCore::Provision::populate_script);
    
    # Once populate-db is completed, indicate to the agent that initially called
    # commit-create-db that we are all-done.
    use CCNCore::Provision::Unspool;
    my $s = new CCNCore::Provision::Unspool($self->p,$master_callback,$master_ticket);
    $s->reply();
    warn "populate_and_continue: completed";
}

1;